
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.mystd.all;

entity ALUBlock is 
    port (
    	clk			: in std_logic;		-- signal takta
    	reset		: in std_logic;		-- RESET signal
        
        
    	M1Bus		: in bus32;			-- interna magistrala M1
    	M2Bus	    : in bus32;			-- interna magistrala M2
    	M3Bus	    : inout bus32;		-- interna magistrala M3
       
       
    	immValue	: in bus32;			-- neposredna vrednost (instructionBlock)
    	opcode		: in bus6;			-- opcode instrukcija (instructionBlock)
        
    	ALUop		: in std_logic;		-- upravljacki signal za rad ALU jedinice
    	aluOut		: in std_logic;		-- upravljacki signal kojim se propusta vrednost ALU rezultata (result)
        								-- na internu magistralu M3Bus
        
    	interrupt	: out std_logic;	-- prekid - aktivan signal ako se desi stackOverflow,
    									-- prekoracenje u generisanje adrese ili nevalidna instrukcija
    	branch		: out std_logic		-- signal koji nam govori da se registar PC mora azurirati								
       									-- signal se dovodi u upravljacku jedinicu
	); 								
     
end ALUBlock;     
        

architecture ALUBlock of ALUBlock is
    
	signal result : bus32;			-- rezultat alu operacije
	signal stack  : stackArray;		-- stek
	signal stackPointer : integer ;  -- pokazivac na vrh steka
    
	-------------------------------------------------------
	--  stack pointer pokazuje na prvu slobodnu lokaciju --
	-- 			 raste ka visim adresama				 --						
	-------------------------------------------------------
       
begin
    	
	process
		variable stackPointerVar : integer:=0;
	begin
    
		wait until clk = '1';
        
		if(reset = '1') then
			---------------------------------
			-- reset ima najvisi prioritet --
			-- inicijalizujemo nulama stek --
			---------------------------------
        
			stackPointer <= 0;
            
			for i in 0 to 255 loop
			stack(i) <= X"00_00_00_00";
			end loop;
            
			-- izlaz alu jedinice koji se cuva, se inicijalizuje nulama
			interrupt <= '0';
			result <= X"00_00_00_00";
		else
			if(ALUop = '1') then
            
			case opcode is
			
				when LOADinstr | STOREinstr | ADDIinstr =>
                    
					-- u slucaju LOAD ili STORE instrukcije
					-- rezultat je adresa sa koje treba procitati/upisati podatak
                        
					-- u slucaju ADDinstrukcije dobija se rezultat
					result <= bus32(unsigned(M1Bus) + (unsigned(immValue)));
                    
				when ADDinstr =>
					-- sabiramo prvi i drugi operand i rezultat cuvemo u registru result
					result <= bus32(unsigned(M1Bus) + unsigned(M2Bus));
                    
				when SUBinstr =>
 					-- oduzimamo od prvog operanda drugi operand i rezultat cuvamo u registru result
					result <= bus32(unsigned(M1Bus) - unsigned(M2Bus));
                    
				when ANDinstr =>
					-- vrsimo logicku operaciju AND izmedju prvog i drugog operanda
					result <= bus32(unsigned(M1Bus) and unsigned(M2Bus));
                    
				when ORinstr =>
					-- vrsimo logicku operaciju OR izmedju prvog i drugog operanda
					result <= bus32(unsigned(M1Bus) or unsigned(M2Bus));
                    
				when XORinstr =>
					-- vrsimo logicku operaciju XOR izmedju prvog i drugog operanda
					result <= bus32(unsigned(M1Bus) xor unsigned(M2Bus));
                    
				when NOTinstr =>
					-- vrsimo negaciju nad prvim operandom
					result <= bus32(not unsigned(M1Bus));
                    
				when SUBIinstr =>
					-- oduzimamo od prvog operanda neposrednu vrednost
					result <= bus32((unsigned(M1Bus)) - (unsigned(immValue)));
                    
				when SHLinstr =>
					-- vrsimo logicko pomeranje u levo rd operanda za $immValue mesta
					result <= bus32(unsigned(M3Bus) sll to_integer(unsigned(immValue)));
                    
				when SHRinstr =>
					-- vrsimo logicko pomeranje u desno rd operanda za $immValue mesta
					result <= bus32(unsigned(M3Bus) srl  to_integer(unsigned(immValue)));
                    
				when SARinstr =>
					-- vrsimo aritmeticko pomeranje u desno rd operanda za $immValue mesta
					result <= bus32(to_stdlogicvector(to_bitvector(std_logic_vector(unsigned(M3Bus))) sra to_integer(unsigned(immValue))));
                    
				when ROLinstr =>
					-- vrsimo rotiranje u levo rd operanda za immValue mesta
					result <= bus32(unsigned(M3Bus) rol to_integer(unsigned(immValue)));
                    
				when RORinstr =>
					-- vrsimo rotiranje u desno rd operanda za immValue mesta
					result <= bus32(unsigned(M3Bus) ror to_integer(unsigned(immValue)));
                    
				when MOVinstr =>
					-- vrsimo obican transfer kroz ALU jedinicu
					result <= bus32(unsigned(M1Bus));
                    
				when MOVIinstr =>
					-- u niza 2 bajta RD operanda se upisuje neposredna oznacena vrednost
					result <= M3Bus(31 downto 16) & immValue(15 downto 0);
                    
				when JMPinstr =>
					-- JMP instrukcija           
					-- prvo proveravamo da li nova adresa moze da stane u PC registar
                        
					if(unsigned(M1Bus) + unsigned(immValue) > X"FFFFFFFF") then
						-- nova adresa ne moze stati u PC registar
						-- generise se prekid zbog greske
						interrupt <= '1';
					else
						result <= bus32(unsigned(M1Bus) + (unsigned(immValue)));
					end if;
                        
				when JSRinstr =>
					-- skok na potprogram
					-- prvo moramo sacuvati PC registar na stek
                        
					-- na linijama M3Bus se nalazi vrednost programskog brojaca
                        
					stack(stackPointer) <= M3Bus;
					stackPointer <= stackPointer + 1;
                        
					-- ako je vrednost veca od 255 dolazi do prekoracenja
					-- pa se generise stackOverFlow signal koji dovodi do generisanja
					-- signala prekida kojim se zaustavlja procesor
                        
					if(stackPointer > 255) then
						interrupt <= '1';
					end if;
                        
					-- nova vrednost PC-a
                        
					if(unsigned(M1Bus) + unsigned(immValue) > X"FFFFFFFF") then
						-- ako nova adresa (adresa potprograma) ne moze da stane u programski brojac PC
						-- generisemo prekid zbog greske
						interrupt <= '1';
					else
						result <= bus32(unsigned(M1Bus) + (unsigned(immValue)));
					end if;
                    
				when RTSinstr =>
					-- povratak iz potprograma
					-- prvo sa steka skidamo vrednost programskog brojaca PC
                        
					-----------------------------------------------------------------
					-- promenljiva stackPointerVar je potrebna 
					-- da ne bi cekali jedan takt da se odradi "update"
					-- dekrementiranje stackPointer-a, vec odma mozemo dobiti
					-- dekrementiranu verziju stackPointer-a i procitati sa steka
					-- vrednost programskog brojaca PC
					-----------------------------------------------------------------
                        
					stackPointerVar := stackPointer - 1;
					stackPointer <= stackPointer - 1;
                        
					-- ako je stack pointer manji od nule
					-- to znaci da je doslo do prekoracenja pa se generise
					-- signal prekida
					if(stackPointerVar <0) then
						interrupt <= '1';
					else
						-- citamo PC sa steka
						result <= stack(stackPointerVar);
					end if;
                        
				when BEQinstr =>
					if(M1Bus = M2Bus) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi na M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
							interrupt <= '1';
                                
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
					end if;	-- kraj if(M1Bus = M2Bus)
                    
				when BNQinstr =>
					if(M1Bus /= M2Bus) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi u M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
                            
							interrupt <= '1';
                            
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
                        
					end if;
                    
				when BLTinstr =>
					-- koristi se signed, jer se radi sa oznacenim velicinama!
					if(signed(M1Bus) < signed(M2Bus)) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi u M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
                            
							interrupt <= '1';
                            
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
					end if;
                    
				when BGTinstr =>
					-- koristi se signed, jer se radi sa oznacenim velicinama!
					if(signed(M1Bus) > signed(M2Bus)) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi u M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
                            
							interrupt <= '1';
                            
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
					end if;
                    
				when BLEinstr =>
					-- koristi se signed, jer se radi sa oznacenim velicinama!
					if(signed(M1Bus) <= signed(M2Bus)) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi u M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
                            
							interrupt <= '1';
                            
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
					end if;
                    
				when BGEinstr =>
					-- koristi se signed, jer se radi sa oznacenim velicinama!
					if(signed(M1Bus) >= signed(M2Bus)) then
						-- u slucaju instrukcija uslovnih skokova
						-- vrednost registra PC se nalazi u M3Bus
						if(unsigned(M3Bus) + unsigned(immValue) > X"FFFFFFFF") then
							-- ako nova adresa ne moze da stane u programski brojac PC
							-- generise se signal prekida
                            
							interrupt <= '1';
                            
						else
							-- adresa koju treba upisati u programski brojac PC
							result <= bus32(unsigned(M3Bus) + (unsigned(immValue)));
						end if;
					end if;
                    
				when HALTinstr =>
					-- HALT instrukcija zaustavlja procesor
					-- njena obrada u kontrolnoj jedinici
				
				when PUSHinstr =>
					-- operand M1Bus se upisuje na stek
					stack(stackPointer) <= M1Bus;
                       
					-- inkrementiramo vrednost stackPointera da pokazuje na sledecu slobodnu adresu
					stackPointerVar := stackPointer;
					stackPointer <= stackPointer + 1;
                        
					-- ako je vrednost veca od 255 dolazi do prekoracenja
					-- pa se generise stackOverFlow signal koji dovodi do generisanja
					-- signala prekida kojim se zaustavlja procesor
                        
					if(stackPointerVar + 1 > 255) then
						interrupt <= '1';
					end if;
                    
				when POPinstr =>
					-- sa steka se skida nova vrenost koju treba upisati u odredisni operand rd
					stackPointerVar := stackPointer - 1;
					stackPointer <= stackPointer - 1;
                        
					-- ako je stack pointer manji od nule
					-- to znaci da je doslo do prekoracenja pa se generise
					-- signal prekida
                        
					if(stackPointerVar < 0) then
						interrupt <= '1';
					else
						result <= stack(stackPointerVar);
					end if;
					
				when others =>
					-- ilegalan kod instrukcije, generise se prekid
					interrupt <= '1';
			
			end case;
                
                
			-- odredjivanja signala branch potrebnih zbog upravljacke jedinice
			if	(	opcode = JMPinstr OR opcode = JSRinstr OR opcode = RTSinstr OR
				(opcode = BEQinstr AND M1Bus = M2Bus) OR
				(opcode = BNQinstr AND M1Bus /= M2Bus) OR
				(opcode = BLEinstr AND signed(M1Bus) <= signed(M2Bus)) OR
				(opcode = BGEinstr AND signed(M1Bus) >= signed(M2Bus)) OR
				(opcode = BLTinstr AND signed(M1Bus) < signed(M2Bus)) OR
				(opcode = BGTinstr AND signed(M1Bus) > signed(M2Bus))
				) then
					branch <='1';
			else
					branch <= '0';
			end if;
			
			end if; -- kraj if(aluOP = '1')
		end if;  -- kraj if(reset = '1')

end process;           
				
	-- propusta se sadrzaj registra result na magistralu M3
	M3Bus <= result when aluOut = '1' else
	(others => 'Z'); 
  	              
end ALUBlock;

